package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"go.manyji.com/lib/log"
	_ "go.manyji.com/lib/log/filelog"
	"go.manyji.com/lib/rand"
	"go.manyji.com/lib/ratelimit"
	"os"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

var (
	tb            = ratelimit.NewTokenBucket()
	gotLock       = sync.Mutex{}
	rechargeTotal = atomic.Int32{}
)

type Simulate struct {
	host     string
	userList []*User
}

func NewSimulate(h string) *Simulate {

	host = h
	return &Simulate{
		host: host,
	}
}

func (s *Simulate) createUsers(count int, args ...interface{}) (int, error) {

	var (
		errMsg         []string
		deviceIdPrefix = fmt.Sprintf("TEST_%v", rand.RandStr(8, rand.UpperChars))
	)

	if len(args) > 0 {
		deviceIdPrefix = args[0].(string)
	}

	for i := 1; i <= count; i++ {
		deviceId := fmt.Sprintf(deviceIdPrefix+"_%v", i)

		if err := FirstVisit(deviceId); err != nil {
			errMsg = append(errMsg, fmt.Sprintf("创建用户 %+v 失败 %+v ", deviceId, err))
			continue
		}
		s.userList = append(s.userList, &User{
			DeviceId: deviceId,
		})
	}

	if len(errMsg) > 0 {
		return len(s.userList), fmt.Errorf("%v", strings.Join(errMsg, " "))
	}

	if len(s.userList) != count {
		return len(s.userList), fmt.Errorf("创建 %v 个用户失败,成功创建 %+v 用户", count, len(s.userList))
	}

	return len(s.userList), nil
}

func (s *Simulate) initLogger(module, deviceId string) (*log.Logger, error) {

	var err error
	_ = os.Mkdir("output", os.ModePerm)
	logFile, err = os.Create(fmt.Sprintf("output/%v_%v.log", module, deviceId))
	if err != nil {
		return nil, err
	}

	return log.New(logFile), nil
}

func (s *Simulate) getFilmList() ([]*FilmDetail, error) {

	categories, err := GetFilmCategories()
	if err != nil {
		return nil, err
	}

	var (
		filmList []*FilmDetail
	)

	for _, v := range categories {
		if f, err := GetFilmList(v.Id); err != nil {
			continue
		} else {
			for _, ff := range f {
				ff.CategoryName = v.Name
				filmList = append(filmList, ff)
			}
		}
	}

	return filmList, nil
}

func (s *Simulate) runUnlock() error {

	filmDatas, err := s.getFilmList()
	if err != nil {
		return err
	}

	var (
		wp        = sync.WaitGroup{}
		userCount = len(s.userList)
	)

	wp.Add(userCount)
	for uIndex, _ := range s.userList {

		go func(uIndex int) {

			defer wp.Done()
			userData := s.userList[uIndex]
			_ = s.unlockOne(userData, filmDatas)

		}(uIndex)
	}
	wp.Wait()

	return nil
}

func (s *Simulate) runRecharge() error {

	actDatas, err := GetActivities("xxx")
	if err != nil {
		return err
	}

	var (
		wp        = sync.WaitGroup{}
		userCount = len(s.userList)
	)

	wp.Add(userCount)
	for uIndex, _ := range s.userList {

		go func(uIndex int) {

			defer wp.Done()
			userData := s.userList[uIndex]
			_ = s.rechargeOne(userData, actDatas)

		}(uIndex)
	}
	wp.Wait()

	return nil
}

func (s *Simulate) unlockOne(userData *User, filmDatas []*FilmDetail) error {

	var (
		randMin                                         = 50
		randMax                                         = 1000
		filmCount                                       = len(filmDatas)
		wp                                              = sync.WaitGroup{}
		msgDetail                                       []interface{}
		msgDetailLock                                   = sync.Mutex{}
		printMsg                                        = map[string]interface{}{}
		unlockInfo                                      = map[string]interface{}{}
		unlockCount                                     = rand.In(randMin, randMax)
		uBefore, uAfter                                 *userRecharge
		rTotal, rFailedTotal, rSuccessTotal             []*UnlockEpisodeRsp
		rTotalLock, rFailedTotalLock, rSuccessTotalLock = sync.Mutex{}, sync.Mutex{}, sync.Mutex{}
		userLogger, _                                   = s.initLogger("unlock", userData.DeviceId)
	)

	if uData, err := GetUserInfo(userData.DeviceId); err != nil {
		printMsg["before"] = map[string]interface{}{
			"err": fmt.Sprintf("err:%+v", err),
		}
	} else {
		uBefore = &userRecharge{
			uData.Coins,
			uData.Bonus,
		}
	}

	unlockCount = filmCount
	wp.Add(unlockCount)
	unlockInfo["unlock_film_count"] = unlockCount
	for i := 1; i <= unlockCount; i++ {
		//randUnlockIndex := rand.In(0, filmCount-1)

		randUnlockIndex := i - 1
		<-time.After(time.Millisecond * 100)
		go func(index, fIndex int) {

			var (
				fData = filmDatas[fIndex]
				msg   = struct {
					Index int         `json:"index"`
					FData *FilmDetail `json:"f_data"`
					Msg   string      `json:"msg"`
				}{
					Index: index,
					FData: fData,
				}
			)

			defer func() {
				msgDetailLock.Lock()
				msgDetail = append(msgDetail, msg)
				msgDetailLock.Unlock()

				wp.Done()
			}()

			if tRsp, sRsp, fRsp, err := s.UnlockFilmEpisode(userData, fData); err != nil {

				rFailedTotalLock.Lock()
				rFailedTotal = append(rFailedTotal, fRsp...)
				rFailedTotalLock.Unlock()

				rTotalLock.Lock()
				rTotal = append(rTotal, tRsp...)
				rTotalLock.Unlock()
				msg.Msg = fmt.Sprintf("failed:%+v", err)
				return
			} else {
				rSuccessTotalLock.Lock()
				rSuccessTotal = append(rSuccessTotal, sRsp...)
				rSuccessTotalLock.Unlock()

				rTotalLock.Lock()
				rTotal = append(rTotal, tRsp...)
				rTotalLock.Unlock()
				msg.Msg = "success"
			}

		}(i, randUnlockIndex)
	}
	wp.Wait()

	if uData, err := GetUserInfo(userData.DeviceId); err != nil {
		printMsg["after"] = map[string]interface{}{
			"err": fmt.Sprintf("err:%+v", err),
		}
	} else {
		uAfter = &userRecharge{
			uData.Coins,
			uData.Bonus,
		}
	}

	var (
		total   = &UserUnlockOrderTotal{}
		success = &UserUnlockOrderTotal{}
		failed  = &UserUnlockOrderTotal{}
	)

	for _, i := range rSuccessTotal {
		success.Add(int64(i.Price))
	}

	for _, i := range rFailedTotal {
		failed.Add(int64(i.Price))
	}

	for _, i := range rTotal {
		total.Add(int64(i.Price))
	}

	unlockInfo["before"] = uBefore
	unlockInfo["after"] = uAfter
	unlockInfo["total"] = total
	unlockInfo["success"] = success
	unlockInfo["failed"] = failed

	var (
		_userLogger = userLogger.Info
	)

	if len(rFailedTotal) > 0 {
		log.Err(fmt.Sprintf("设备ID:%v,错误", userData.DeviceId))
		_userLogger = userLogger.Err
	}

	unlockInfo["list"] = msgDetail
	printMsg["unlock_info"] = unlockInfo
	printMsg["device_id"] = userData.DeviceId
	bb, err1 := json.MarshalIndent(printMsg, " ", " ")
	if err1 != nil {
		log.Err("解析Json失败:%+v", err1)
		return nil
	}

	_userLogger(fmt.Sprintf("%v", string(bb)))
	return nil
}

func (s *Simulate) rechargeOne(userData *User, actDatas []*ActivityData) error {

	var (
		randMin                             = 500
		randMax                             = 1000
		actCount                            = len(actDatas)
		wp                                  = sync.WaitGroup{}
		msgDetail                           []interface{}
		msgDetailLock                       = sync.Mutex{}
		printMsg                            = map[string]interface{}{}
		rechargeInfo                        = map[string]interface{}{}
		rechargeCount                       = rand.In(randMin, randMax)
		uBefore, uAfter                     *userRecharge
		rTotal, rFailedTotal, rSuccessTotal []int
		rFailedTotalLock, rSuccessTotalLock = sync.Mutex{}, sync.Mutex{}
		userLogger, _                       = s.initLogger("recharge", userData.DeviceId)
	)

	if uData, err := GetUserInfo(userData.DeviceId); err != nil {
		printMsg["before"] = map[string]interface{}{
			"err": fmt.Sprintf("err:%+v", err),
		}
	} else {
		uBefore = &userRecharge{
			uData.Coins,
			uData.Bonus,
		}
		printMsg["before"] = uBefore
	}

	rechargeCount = 1
	wp.Add(rechargeCount)
	printMsg["count"] = rechargeCount
	rechargeInfo["count"] = rechargeCount
	for i := 1; i <= rechargeCount; i++ {
		<-time.After(time.Millisecond * 20)

		randActIndex := rand.In(0, actCount-1)
		rTotal = append(rTotal, randActIndex)
		go func(index, aIndex int) {

			var (
				aData = actDatas[aIndex]
				msg   = struct {
					Index int           `json:"index"`
					AData *ActivityData `json:"a_data"`
					Msg   string        `json:"msg"`
				}{
					Index: index,
					AData: aData,
				}
			)

			defer func() {
				msgDetailLock.Lock()
				msgDetail = append(msgDetail, msg)
				msgDetailLock.Unlock()

				wp.Done()
			}()

			if err := s.Recharge(userData, aData); err != nil {

				rFailedTotalLock.Lock()
				rFailedTotal = append(rFailedTotal, aIndex)
				rFailedTotalLock.Unlock()
				msg.Msg = fmt.Sprintf("failed:%+v", err)
				return
			}

			rSuccessTotalLock.Lock()
			rSuccessTotal = append(rSuccessTotal, aIndex)
			rSuccessTotalLock.Unlock()
			msg.Msg = "success"
		}(i, randActIndex)
	}
	wp.Wait()

	if uData, err := GetUserInfo(userData.DeviceId); err != nil {
		printMsg["after"] = map[string]interface{}{
			"err": fmt.Sprintf("err:%+v", err),
		}
	} else {
		uAfter = &userRecharge{
			uData.Coins,
			uData.Bonus,
		}
	}

	var (
		total   = &UserActivityOrderTotal{}
		success = &UserActivityOrderTotal{}
		failed  = &UserActivityOrderTotal{}
	)
	for _, i := range rTotal {
		total.Add(actDatas[i].Price, actDatas[i].Coins, actDatas[i].Bonus)
	}
	for _, i := range rSuccessTotal {
		success.Add(actDatas[i].Price, actDatas[i].Coins, actDatas[i].Bonus)
	}

	for _, i := range rFailedTotal {
		failed.Add(actDatas[i].Price, actDatas[i].Coins, actDatas[i].Bonus)
	}

	rechargeInfo["before"] = uBefore
	rechargeInfo["after"] = uAfter
	rechargeInfo["total"] = map[string]interface{}{
		"total": total,
		"count": len(rTotal),
	}
	rechargeInfo["success"] = map[string]interface{}{
		"total": success,
		"count": len(rSuccessTotal),
	}
	rechargeInfo["failed"] = map[string]interface{}{
		"total": failed,
		"count": len(rFailedTotal),
	}

	var (
		_userLogger = userLogger.Info
	)

	if len(rFailedTotal) > 0 {
		log.Err(fmt.Sprintf("设备ID:%v,充值错误", userData.DeviceId))
		_userLogger = userLogger.Err
	}

	rechargeInfo["list"] = msgDetail
	printMsg["recharge_info"] = rechargeInfo
	printMsg["device_id"] = userData.DeviceId
	bb, err1 := json.MarshalIndent(printMsg, " ", " ")
	if err1 != nil {
		log.Err("解析Json失败:%+v", err1)
		return nil
	}

	_userLogger(fmt.Sprintf("%v", string(bb)))
	return nil
}

func (s *Simulate) UnlockFilmEpisode(userData *User, fData *FilmDetail) ([]*UnlockEpisodeRsp, []*UnlockEpisodeRsp, []*UnlockEpisodeRsp, error) {

	var maxENo int
	filmRsp, err := GetFilmDetail(int64(fData.Id), userData.DeviceId)
	if err != nil {
		return nil, nil, nil, err
	}
	for _, v := range filmRsp.UnlockedFilmEpisodes {
		if v.EpisodeNo > maxENo {
			maxENo = v.EpisodeNo
		}
	}

	if maxENo == fData.Episodes {
		return nil, nil, nil, fmt.Errorf(fmt.Sprintf("设备ID:%+v,已经解锁影片%+v 全部剧集", userData.DeviceId, filmRsp.Name))
	}

	var (
		auto  = "0"
		tRsp  []*UnlockEpisodeRsp
		sRsp  []*UnlockEpisodeRsp
		fRsp  []*UnlockEpisodeRsp
		randE = rand.In(maxENo+1, fData.Episodes)
	)

	for i := maxENo + 1; i <= randE; i++ {

		price, err := GetFilmEpisodeCoins(fData.Id, strconv.Itoa(i), userData.DeviceId, auto)
		if err != nil {
			return tRsp, sRsp, fRsp, errors.New(fmt.Sprintf("get coins failed:%+v episode no:%+v", err, i))
		}

		tRsp = append(tRsp, price)

		if err := UnlockFilmEpisode(fData.Id, strconv.Itoa(i), userData.DeviceId, auto); err != nil {
			fRsp = append(fRsp, price)
			fRsp = append(fRsp, &UnlockEpisodeRsp{
				Price: price.Price,
				Msg:   fmt.Sprintf("unlock failed:%+v episode no:%+v", err, i),
			})
			continue
		}
		sRsp = append(sRsp, price)
	}
	return tRsp, sRsp, fRsp, nil
}

func (s *Simulate) Recharge(userData *User, actData *ActivityData) error {

	var (
		capacity   = 100             // 令牌桶容量
		genPeriod  = 1 * time.Second // 令牌生成周期
		genCount   = 100             // 每周期生成的令牌数量
		takeTokens = 1               // 每次获取的令牌数量
		got        bool
	)

	gotLock.Lock()
	for !got {
		state := tb.Acquire(capacity, genPeriod, genCount, takeTokens)
		got = state.OK()
		if !got {
			<-time.After(state.NextGenAfter())
		}
	}

	gotLock.Unlock()

	rData, err := Recharge(actData.Id, userData.DeviceId)
	if err != nil {
		return err
	}

	for rechargeTotal.Load() >= 200 {
		time.Sleep(time.Millisecond * 5)
	}
	rechargeTotal.Add(1)
	defer rechargeTotal.Add(-1)
	if err := RechargeCallBack(2, 500, rData.ID, userData.DeviceId); err != nil {
		return err
	}

	return nil
}

func concurrentRun(simulate string) {

	var (
		userCount = 1
		//s         = NewSimulate("http://127.0.0.1:10087/")
		s = NewSimulate("http://192.168.1.30:20086/")
		//s = NewSimulate("https://api.shortstv.cc/")
	)

	if _, err := s.createUsers(userCount); err != nil {
		log.Err(err)
	}

	if simulate == "recharge" {
		if err := s.runRecharge(); err != nil {
			log.Err(fmt.Sprintf("%v", err.Error()))
		}
	}

	if simulate == "unlock" {
		if err := s.runRecharge(); err != nil {
			log.Err(fmt.Sprintf("%v", err.Error()))
		}
		if err := s.runUnlock(); err != nil {
			log.Err(fmt.Sprintf("%v", err.Error()))
		}
	}

	return
}
